package com.jorado.redis;

import com.jorado.core.utility.ConvertUtils;
import com.jorado.core.utility.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.ListPosition;
import redis.clients.jedis.params.SetParams;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class RedisClientImpl implements RedisClientInterface {

    public static final String XX = "xx";

    public static final String NX = "nx";

    public static final String PX = "px";

    public static final String EX = "ex";
    // redisClient实例
    private JedisCluster redisClient = null;
    // redis连接字符串
    private String redisClusters;
    // 连接超时设置
    private int connectTimeout;
    // 读取超时设置
    private int socketTimeout;
    // 最大重试次数
    private int maxRedirections;
    // 连接池配置参数(最大的连接数)
    private int maxTotal;
    // 连接池配置参数(最大空闲连接)
    private int maxIdle;
    // 连接池配置参数(最少空闲连接)
    private int minIdle;
    // 获取连接池中连接的最大等待时间
    private int maxWaitMillis;
    // 是否熔断
    private boolean isFuse;
    // 熔断时间
    private long fuseTime;

    public RedisClientImpl(String redisClusters) {
        this(redisClusters, false);
    }

    public RedisClientImpl(String redisClusters, boolean isFuse) {
        this(redisClusters, DEFAULT_CONNECT_TIMEOUT, DEFAULT_SOCKET_TIMEOUT, DEFAULT_MAX_REDIRECTIONS,
                DEFAULT_MAX_TOTAL, DEFAULT_MAX_IDLE, DEFAULT_MIN_IDLE, DEFAULT_MAX_WAIT_MILLIS, isFuse,
                DEFAULT_FUSE_MILLIS);
    }

    public RedisClientImpl(String redisClusters, boolean isFuse, long fuseTime) {
        this(redisClusters, DEFAULT_CONNECT_TIMEOUT, DEFAULT_SOCKET_TIMEOUT, DEFAULT_MAX_REDIRECTIONS,
                DEFAULT_MAX_TOTAL, DEFAULT_MAX_IDLE, DEFAULT_MIN_IDLE, DEFAULT_MAX_WAIT_MILLIS, isFuse, fuseTime);
    }

    public RedisClientImpl(String redisClusters, int connectTimeout, int socketTimeout, int maxRedirections) {
        this(redisClusters, connectTimeout, socketTimeout, maxRedirections, DEFAULT_MAX_TOTAL, DEFAULT_MAX_IDLE,
                DEFAULT_MIN_IDLE, DEFAULT_MAX_WAIT_MILLIS, false, DEFAULT_FUSE_MILLIS);
    }

    public RedisClientImpl(String redisClusters, int connectTimeout, int socketTimeout, int maxRedirections,
                           boolean isFuse) {
        this(redisClusters, connectTimeout, socketTimeout, maxRedirections, DEFAULT_MAX_TOTAL, DEFAULT_MAX_IDLE,
                DEFAULT_MIN_IDLE, DEFAULT_MAX_WAIT_MILLIS, isFuse, DEFAULT_FUSE_MILLIS);
    }

    public RedisClientImpl(String redisClusters, int connectTimeout, int socketTimeout, int maxRedirections,
                           boolean isFuse, long fuseTime) {
        this(redisClusters, connectTimeout, socketTimeout, maxRedirections, DEFAULT_MAX_TOTAL, DEFAULT_MAX_IDLE,
                DEFAULT_MIN_IDLE, DEFAULT_MAX_WAIT_MILLIS, isFuse, fuseTime);
    }

    public RedisClientImpl(String redisClusters, int connectTimeout, int socketTimeout, int maxRedirections,
                           int maxTotal, int maxIdle, int minIdle, int maxWaitMillis) {
        this(redisClusters, connectTimeout, socketTimeout, maxRedirections, maxTotal, maxIdle, minIdle, maxWaitMillis,
                false, 0);
    }

    public RedisClientImpl(String redisClusters, int connectTimeout, int socketTimeout, int maxRedirections,
                           int maxTotal, int maxIdle, int minIdle, int maxWaitMillis, boolean isFuse) {
        this(redisClusters, connectTimeout, socketTimeout, maxRedirections, maxTotal, maxIdle, minIdle, maxWaitMillis,
                isFuse, DEFAULT_FUSE_MILLIS);
    }

    public RedisClientImpl(String redisClusters, int connectTimeout, int socketTimeout, int maxRedirections,
                           int maxTotal, int maxIdle, int minIdle, int maxWaitMillis, boolean isFuse, long fuseTime) {
        // redis连接字符串
        this.redisClusters = redisClusters;
        // 设置连接超时时间
        this.connectTimeout = connectTimeout;
        // 设置读取超时时间
        this.socketTimeout = socketTimeout;
        // 最大重试次数
        if (maxRedirections < 2) {
            maxRedirections = 2;
        }
        this.maxRedirections = maxRedirections;
        // 连接池配置参数(最大的连接数)
        this.maxTotal = maxTotal;
        // 连接池配置参数(最少空闲连接)
        this.maxIdle = maxIdle;
        // 连接池配置参数(最少空闲连接)
        this.minIdle = minIdle;
        // 获取连接池中连接的最大等待时间
        this.maxWaitMillis = maxWaitMillis;
        // 是否熔断标记
        this.isFuse = isFuse;
        if (this.isFuse) {
            this.fuseTime = fuseTime;
        } else {
            this.fuseTime = Long.MIN_VALUE;
        }
        // 初始化redis客户端
        initRedisClient();
    }

    private Set<HostAndPort> getRedisClusterNodes(String redisClusters) {
        // 采用构造函数创建对象，或者构造函数方式配置spring的bean时，该参数不允许是空字符串
        if (redisClusters == null) {
            throw new IllegalArgumentException("构造函数的redisClusters参数值为空!");
        } else if (redisClusters.trim().length() <= 0) {
            throw new IllegalArgumentException("构造函数的redisClusters参数值" + redisClusters + "不合法!");
        }
        // 对连接字符串进行分解处理成Set<HostAndPort>
        Set<HostAndPort> clusterNodes = new HashSet<>();
        String[] hostAndPorts = redisClusters.split(",");
        for (String hostAndPort : hostAndPorts) {
            if (hostAndPort == null || hostAndPort.trim().length() <= 0) {
                throw new IllegalArgumentException("构造函数的redisClusters参数" + redisClusters + "不合法!");
            }
            if (hostAndPort.contains(":")) {
                String[] hostPort = hostAndPort.split(":");
                if (hostPort[0] == null || hostPort[0].length() <= 0) {
                    throw new IllegalArgumentException("构造函数的redisClusters参数" + redisClusters + "不合法!");
                }
                if (hostPort[1] == null || hostPort[1].length() <= 0) {
                    throw new IllegalArgumentException("构造函数的redisClusters参数" + redisClusters + "不合法!");
                }
                if (!StringUtils.isNumber(hostPort[1].trim())) {
                    throw new IllegalArgumentException("构造函数的redisClusters参数" + redisClusters + "不合法!");
                }
                clusterNodes.add(new HostAndPort(hostPort[0].trim(), ConvertUtils.toIntValue(hostPort[1].trim())));
            } else {
                clusterNodes.add(new HostAndPort(hostAndPort.trim(), DEFAULT_REDIS_PORT));
            }
        }
        return clusterNodes;
    }

    /**
     * 初始化redis客户端
     */
    public synchronized void initRedisClient() {
        if (this.redisClient == null) {
            Set<HostAndPort> clusterNodes = getRedisClusterNodes(this.redisClusters);
            GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
            poolConfig.setMaxTotal(this.maxTotal);
            poolConfig.setMaxIdle(this.maxIdle);
            poolConfig.setMinIdle(this.minIdle);
            poolConfig.setMaxWaitMillis(this.maxWaitMillis);
            if (this.maxTotal != this.maxIdle || this.maxIdle != this.minIdle) {
                poolConfig.setTimeBetweenEvictionRunsMillis(30 * 60 * 1000);
            }
            try {
                // 创建redisCluster对象
                if (AbstractRedisClusterInitializer.isjoradoJedisExists()) {
                    this.redisClient = new JoradoJedisRedisClusterInitializer().init(clusterNodes, this.connectTimeout, this.socketTimeout,
                            this.maxRedirections, poolConfig);
                } else {
                    this.redisClient = new JedisRedisClusterInitializer().init(clusterNodes, this.connectTimeout, this.socketTimeout,
                            this.maxRedirections, poolConfig);
                }
            } catch (Exception exp) {
                throw new IllegalStateException("连接Redis集群出现异常", exp);
            }
        }
    }

    @Override
    public void close() {
        getRedisClient().close();
    }

    @Override
    public Long del(String key) {
        return this.getRedisClient().del(key);
    }

    @Override
    public Boolean exists(String key) {
        return this.getRedisClient().exists(key);
    }

    @Override
    public Long expire(final String key, final int seconds) {
        return this.getRedisClient().expire(key, seconds);
    }

    @Override
    public Long expireAt(final String key, final long unixTime) {
        return this.getRedisClient().expireAt(key, unixTime);
    }

    @Override
    public Long persist(final String key) {
        return this.getRedisClient().persist(key);
    }

    @Override
    public Long pexpire(final String key, final long milliseconds) {
        return this.getRedisClient().pexpire(key, milliseconds);
    }

    @Override
    public Long pexpireAt(final String key, final long millisecondsTimestamp) {
        return this.getRedisClient().pexpireAt(key, millisecondsTimestamp);
    }

    @Override
    public Long ttl(final String key) {
        return this.getRedisClient().ttl(key);
    }

    @Override
    public String type(final String key) {
        return this.getRedisClient().type(key);
    }

    @Override
    public String get(final String key) {
        return this.getRedisClient().get(key);
    }

    @Override
    public String set(final String key, final String value) {
        return getRedisClient().set(key, value);
    }

    @Override
    public String set(String key, String value, String nxxx, String expx, long time) {
        SetParams setParams = SetParams.setParams();
        if (NX.equalsIgnoreCase(nxxx)) {
            setParams = setParams.nx();
        } else if (XX.equalsIgnoreCase(nxxx)) {
            setParams = setParams.xx();
        } else {
            throw new IllegalArgumentException("Parameter `nxxx` optional values are  NX, XX");
        }
        if (PX.equalsIgnoreCase(expx)) {
            setParams = setParams.px(time);
        } else if (EX.equalsIgnoreCase(expx)) {
            setParams = setParams.ex((int) time);
        } else {
            throw new IllegalArgumentException("Parameter `expx` optional values are  PX, EX");
        }
        return getRedisClient().set(key, value, setParams);
    }

    @Override
    public String set(String key, String value, String expx, long time) {
        SetParams setParams = SetParams.setParams();
        if (PX.equalsIgnoreCase(expx)) {
            setParams = setParams.px(time);
        } else if (EX.equalsIgnoreCase(expx)) {
            setParams = setParams.ex((int) time);
        } else {
            throw new IllegalArgumentException("Parameter `expx` optional values are  PX, EX");
        }
        return getRedisClient().set(key, value, setParams);
    }

    @Override
    public String setEx(String key, String value, int time) {
        return getRedisClient().setex(key, time, value);
    }

    @Override
    public String pSetEx(String key, String value, long time) {
        return getRedisClient().psetex(key, time, value);
    }

    @Override
    public Long incr(final String key) {
        return this.getRedisClient().incr(key);
    }

    @Override
    public Long incrBy(final String key, final long integer) {
        return this.getRedisClient().incrBy(key, integer);
    }

    @Override
    public Long setnx(final String key, final String value) {
        return this.getRedisClient().setnx(key, value);
    }

    @Override
    public String setex(final String key, final int seconds, final String value) {
        return this.getRedisClient().setex(key, seconds, value);
    }

    @Override
    public Long decr(final String key) {
        return this.getRedisClient().decr(key);
    }

    @Override
    public Long decrBy(final String key, final long integer) {
        return this.getRedisClient().decrBy(key, integer);
    }

    @Override
    public String getSet(final String key, final String value) {
        return this.getRedisClient().getSet(key, value);
    }

    @Override
    public Long hdel(final String key, final String... field) {
        return this.getRedisClient().hdel(key, field);
    }

    @Override
    public Boolean hexists(final String key, final String field) {
        return this.getRedisClient().hexists(key, field);
    }

    @Override
    public String hget(final String key, final String field) {
        return this.getRedisClient().hget(key, field);
    }

    @Override
    public Map<String, String> hgetAll(final String key) {
        return this.getRedisClient().hgetAll(key);
    }

    @Override
    public Long hincrBy(final String key, final String field, final long value) {
        return this.getRedisClient().hincrBy(key, field, value);
    }

    @Override
    public Set<String> hkeys(final String key) {
        return this.getRedisClient().hkeys(key);
    }

    @Override
    public Long hlen(final String key) {
        return this.getRedisClient().hlen(key);
    }

    @Override
    public List<String> hmget(final String key, final String... fields) {
        return this.getRedisClient().hmget(key, fields);
    }

    @Override
    public String hmset(final String key, final Map<String, String> hash) {
        return this.getRedisClient().hmset(key, hash);
    }

    @Override
    public Long hset(final String key, final String field, final String value) {
        return this.getRedisClient().hset(key, field, value);
    }

    @Override
    public Long hsetnx(final String key, final String field, final String value) {
        return this.getRedisClient().hsetnx(key, field, value);
    }

    @Override
    public List<String> hvals(final String key) {
        return this.getRedisClient().hvals(key);
    }

    @Override
    public String lindex(String key, long index) {
        return this.getRedisClient().lindex(key, index);
    }

    @Override
    public Long llen(final String key) {
        return this.getRedisClient().llen(key);
    }

    @Override
    public String lpop(final String key) {
        return this.getRedisClient().lpop(key);
    }

    @Override
    public List<String> blpop(final int timeout, final String key) {
        return this.getRedisClient().blpop(timeout, key);
    }

    @Override
    public List<String> brpop(final int timeout, final String key) {
        return this.getRedisClient().brpop(timeout, key);
    }

    @Override
    public Long lpush(final String key, final String... string) {
        return this.getRedisClient().lpush(key, string);
    }

    @Override
    public Long lpushx(final String key, final String... string) {
        return this.getRedisClient().lpushx(key, string);
    }

    @Override
    public List<String> lrange(final String key, final long start, final long end) {
        return this.getRedisClient().lrange(key, start, end);
    }

    @Override
    public Long lrem(final String key, final long count, final String value) {
        return this.getRedisClient().lrem(key, count, value);
    }

    @Override
    public String lset(final String key, final long index, final String value) {
        return this.getRedisClient().lset(key, index, value);
    }

    @Override
    public String ltrim(final String key, final long start, final long end) {
        return this.getRedisClient().ltrim(key, start, end);
    }

    @Override
    public String rpop(final String key) {
        return this.getRedisClient().rpop(key);
    }

    @Override
    public Long rpush(final String key, final String... string) {
        return this.getRedisClient().rpush(key, string);
    }

    @Override
    public Long rpushx(final String key, final String... string) {
        return this.getRedisClient().rpushx(key, string);
    }

    @Override
    public Long sadd(final String key, final String... member) {
        return this.getRedisClient().sadd(key, member);
    }

    @Override
    public Long scard(final String key) {
        return this.getRedisClient().scard(key);
    }

    @Override
    public Boolean sismember(final String key, final String member) {
        return this.getRedisClient().sismember(key, member);
    }

    @Override
    public Set<String> smembers(final String key) {
        return this.getRedisClient().smembers(key);
    }

    @Override
    public String spop(final String key) {
        return this.getRedisClient().spop(key);
    }

    @Override
    public Set<String> spop(final String key, final long count) {
        return this.getRedisClient().spop(key, count);
    }

    @Override
    public String srandmember(final String key) {
        return this.getRedisClient().srandmember(key);
    }

    @Override
    public Long srem(final String key, final String... member) {
        return this.getRedisClient().srem(key, member);
    }

    @Override
    public Long zadd(final String key, final Map<String, Double> scoreMembers) {
        return this.getRedisClient().zadd(key, scoreMembers);
    }

    @Override
    public Long zadd(final String key, final double score, final String member) {
        return this.getRedisClient().zadd(key, score, member);
    }

    @Override
    public Long zcard(final String key) {
        return this.getRedisClient().zcard(key);
    }

    @Override
    public Long zcount(final String key, final String min, final String max) {
        return this.getRedisClient().zcount(key, min, max);
    }

    @Override
    public Long zcount(final String key, final double min, final double max) {
        return this.getRedisClient().zcount(key, min, max);
    }

    @Override
    public Double zincrby(final String key, final double score, final String member) {
        return this.getRedisClient().zincrby(key, score, member);
    }

    @Override
    public Set<String> zrange(final String key, final long start, final long end) {
        return this.getRedisClient().zrange(key, start, end);
    }

    @Override
    public Set<String> zrangeByScore(final String key, final String min, final String max) {
        return this.getRedisClient().zrangeByScore(key, min, max);
    }

    @Override
    public Set<String> zrangeByScore(final String key, final double min, final double max) {
        return this.getRedisClient().zrangeByScore(key, min, max);
    }

    @Override
    public Long zrank(final String key, final String member) {
        return this.getRedisClient().zrank(key, member);
    }

    @Override
    public Long zrem(final String key, final String... member) {
        return this.getRedisClient().zrem(key, member);
    }

    @Override
    public Long zremrangeByRank(final String key, final long start, final long end) {
        return this.getRedisClient().zremrangeByRank(key, start, end);
    }

    @Override
    public Long zremrangeByScore(final String key, final double start, final double end) {
        return this.getRedisClient().zremrangeByScore(key, start, end);
    }

    @Override
    public Set<String> zrevrange(final String key, final long start, final long end) {
        return this.getRedisClient().zrevrange(key, start, end);
    }

    @Override
    public Set<String> zrevrangeByScore(final String key, final String max, final String min) {
        return this.getRedisClient().zrevrangeByScore(key, max, min);
    }

    @Override
    public Set<String> zrevrangeByScore(final String key, final double max, final double min) {
        return this.getRedisClient().zrevrangeByScore(key, max, min);
    }

    @Override
    public Long zrevrank(final String key, final String member) {
        return this.getRedisClient().zrevrank(key, member);
    }

    @Override
    public Double zscore(final String key, final String member) {
        return this.getRedisClient().zscore(key, member);
    }

    @Override
    public Object eval(String script, List<String> keys, List<String> args) {
        return this.getRedisClient().eval(script, keys, args);
    }

    @Override
    public Long lbeforeinsert(String key, String pivot, String value) {
        return getRedisClient().linsert(key, ListPosition.BEFORE, pivot, value);
    }

    @Override
    public Long lafterinsert(String key, String pivot, String value) {
        return getRedisClient().linsert(key, ListPosition.AFTER, pivot, value);
    }

    @Override
    public JedisCluster getRedisClient() {
        return redisClient;
    }

    public int getConnectTimeout() {
        return connectTimeout;
    }

    public void setConnectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
    }

    public int getSocketTimeout() {
        return socketTimeout;
    }

    public void setSocketTimeout(int socketTimeout) {
        this.socketTimeout = socketTimeout;
    }

    public int getMaxRedirections() {
        return maxRedirections;
    }

    public void setMaxRedirections(int maxRedirections) {
        this.maxRedirections = maxRedirections;
    }

    public int getMaxTotal() {
        return maxTotal;
    }

    public void setMaxTotal(int maxTotal) {
        this.maxTotal = maxTotal;
    }

    public int getMaxIdle() {
        return maxIdle;
    }

    public void setMaxIdle(int maxIdle) {
        this.maxIdle = maxIdle;
    }

    public int getMinIdle() {
        return minIdle;
    }

    public void setMinIdle(int minIdle) {
        this.minIdle = minIdle;
    }

    public int getMaxWaitMillis() {
        return maxWaitMillis;
    }

    public void setMaxWaitMillis(int maxWaitMillis) {
        this.maxWaitMillis = maxWaitMillis;
    }


}
